package psp.linearRegression;

import java.util.LinkedList;
import java.util.ListIterator;

public class LinearRegressionCalculation {

	private Float beta0 = null;
	private Float beta1 = null;
	
	private LinkedList<Float> xNumbers = null;
	private LinkedList<Float> yNumbers = null;
	
	private Float xAverage = null;
	private Float yAverage = null;
	private Float sumXandY = null;
	private Float sumXSquare = null;
	private int numberOfItems = 0;
	
	public LinearRegressionCalculation(LinkedList<Float> xNumbers, LinkedList<Float> yNumbers) 
		throws DifferentNumberOfElementsException {
		
		this.setXNumbers(xNumbers);
		this.setYNumbers(yNumbers);
		
		this.calculateBeta1();
		this.calculateBeta0();
	}
	
	/**
	 * Calculate the value of Beta1 used in linear regression
	 * @throws DifferentNumberOfElementsException Exception thrown when the user gives different number of element in each list
	 */
	private void calculateBeta1() throws DifferentNumberOfElementsException {
		this.beta1 =
			(this.getSumXandY() - this.getNumberOfItems() * this.getXAverage() * this.getYAverage()) /
			(this.getSumXSquare() - this.getNumberOfItems() * this.getXAverage() * this.getXAverage());
	}

	/**
	 * Calculate the value of Beta0 used in linear regression
	 * @throws DifferentNumberOfElementsException Exception thrown when the user gives different number of element in each list
	 */
	private void calculateBeta0() throws DifferentNumberOfElementsException {
		// Check if Beta1 was already calculated. If not, it is calculated
		if (this.getBeta1() == null)
			this.calculateBeta1();
			
		this.beta0 =
			this.getYAverage() - this.getBeta1() * this.getXAverage();
	}

	/**
	 * Return the number of pair in the linear regression
	 * @return number of items
	 */
	private int getNumberOfItems() {
		return this.numberOfItems;
	}
	
	/**
	 * Calculates the sum of the multiplication of x and y
	 * @return sum of elements
	 * @throws DifferentNumberOfElementsException Exception thrown when the user gives different number of element in each list 
	 */
	private Float getSumXandY() throws DifferentNumberOfElementsException {
		if (this.sumXandY == null) {
			Float sum = 0.0f;
			
			ListIterator<Float> xNumbersIterator = this.getXNumbers().listIterator();
			ListIterator<Float> yNumbersIterator = this.getYNumbers().listIterator();
			
			while (xNumbersIterator.hasNext() && yNumbersIterator.hasNext()) {
				sum += xNumbersIterator.next() * yNumbersIterator.next();
				this.numberOfItems++;
			}
		
			// Check if both lists have the same numbers of elements
			if (xNumbersIterator.hasNext() || yNumbersIterator.hasNext())
				throw new DifferentNumberOfElementsException();
			
			this.sumXandY = sum;
		}
		
		return this.sumXandY;
	}
	
	/**
	 * Calculates the sum of the square of each X-element
	 * @return sum of the square of X
	 */
	private Float getSumXSquare() {
		if (this.sumXSquare == null) {
			Float sum = 0.0f;
			Float current = null;
			
			ListIterator<Float> xNumbersIterator = this.getXNumbers().listIterator();
			
			while (xNumbersIterator.hasNext()) {
				current = xNumbersIterator.next();
				sum += current * current;
			}
			this.sumXSquare = sum;
		}
		
		return this.sumXSquare;
	}
	
	/**
	 * Calculates the average of X-elements
	 * @return average of X-elements
	 */
	private Float getXAverage() {
		if (this.xAverage == null)
			this.xAverage = this.calculateAverage(this.getXNumbers());
		return this.xAverage;
	}
	
	/**
	 * Calculates the average of Y-elements
	 * @return average of Y-elements
	 */
	private Float getYAverage() {
		if (this.yAverage == null)
			this.yAverage = this.calculateAverage(this.getYNumbers());
		return this.yAverage;
	}
	
	/**
	 * Calculates the average of items in a list
	 * @param list List of items that will be calculates the average
	 * @return average of the items
	 */
	private Float calculateAverage(LinkedList<Float> list) {
		Float sum = 0.0f;
		
		ListIterator<Float> listIterator = list.listIterator();
		
		while (listIterator.hasNext()){
			sum += listIterator.next();
		}
		
		Float average = sum / this.getNumberOfItems();
		
		return average;
	}
	
	/**
	 * Value of Beta0
	 * @return Beta0
	 */
	public Float getBeta0() {
		return this.beta0;
	}
	
	/**
	 * Value of Beta1
	 * @return Beta1
	 */
	public Float getBeta1() {
		return this.beta1;
	}
	
	/**
	 * Set the variable of X-elements
	 * @param value variable with value
	 */
	private void setXNumbers(LinkedList<Float> value) {
		this.xNumbers = value;
	}
	
	/**
	 * Returns X-elements
	 * @return X-elements
	 */
	private LinkedList<Float> getXNumbers() {
		return this.xNumbers;
	}

	/**
	 * Set the variable of Y-elements
	 * @param value variable with value
	 */
	private void setYNumbers(LinkedList<Float> value) {
		this.yNumbers = value;
	}
	
	/**
	 * Returns Y-elements
	 * @return Y-elements
	 */
	private LinkedList<Float> getYNumbers() {
		return this.yNumbers;
	}
}
